package com.speljohan.rsbot.script;

import java.awt.Point;
import java.awt.Rectangle;

import com.speljohan.rsbot.accessors.Interface;
import com.speljohan.rsbot.accessors.Node;
import com.speljohan.rsbot.bot.Bot;
import com.speljohan.rsbot.script.wrappers.RSTile;

public class Calculations {
	private static Rectangle GAMESCREEN = new Rectangle(4, 4, 512, 334);
	private static final int[] CURVESIN = new int[2048];
	private static final int[] CURVECOS = new int[2048];

	static {
		for (int i = 0; i < 2048; i++) {
			Calculations.CURVESIN[i] = (int) (65536.0 * Math
					.sin(i * 0.0030679615));
			Calculations.CURVECOS[i] = (int) (65536.0 * Math
					.cos(i * 0.0030679615));
		}
	}

	public static boolean canReach(final int startX, final int startY,
			final int destX, final int destY, final boolean isObject) {
		// Documentation part:
		// The blocks info
		// When you can walk freely it's 0, also used to create a noclip
		final int[][] via = new int[104][104];
		final int[][] cost = new int[104][104];
		final int[] tileQueueX = new int[4000];
		final int[] tileQueueY = new int[4000];

		for (int xx = 0; xx < 104; xx++) {
			for (int yy = 0; yy < 104; yy++) {
				via[xx][yy] = 0;
				cost[xx][yy] = 99999999;
			}
		}

		int curX = startX;
		int curY = startY;
		via[startX][startY] = 99;
		cost[startX][startY] = 0;
		int head = 0;
		int tail = 0;
		tileQueueX[head] = startX;
		tileQueueY[head] = startY;
		head++;
		final int pathLength = tileQueueX.length;
		final int blocks[][] = Bot.getClient().getGroundDataArray()[Bot
		                                                            .getClient().getPlane()].getBlocks();
		while (tail != head) {
			curX = tileQueueX[tail];
			curY = tileQueueY[tail];

			if (!isObject && curX == destX && curY == destY) {
				return true;
			} else if (isObject) {
				if (curX == destX && curY == destY + 1 || curX == destX
						&& curY == destY - 1 || curX == destX + 1
						&& curY == destY || curX == destX - 1 && curY == destY) {
					return true;
				}
			}
			tail = (tail + 1) % pathLength;

			// Big and ugly block of code
			final int thisCost = cost[curX][curY] + 1;
			// Can go south (by determining, whether the north side of the
			// south tile is blocked :P)
			if (curY > 0 && via[curX][curY - 1] == 0
					&& (blocks[curX][curY - 1] & 0x1280102) == 0) {
				tileQueueX[head] = curX;
				tileQueueY[head] = curY - 1;
				head = (head + 1) % pathLength;
				via[curX][curY - 1] = 1;
				cost[curX][curY - 1] = thisCost;
			}
			// Can go west
			if (curX > 0 && via[curX - 1][curY] == 0
					&& (blocks[curX - 1][curY] & 0x1280108) == 0) {
				tileQueueX[head] = curX - 1;
				tileQueueY[head] = curY;
				head = (head + 1) % pathLength;
				via[curX - 1][curY] = 2;
				cost[curX - 1][curY] = thisCost;
			}
			// Can go north
			if (curY < 104 - 1 && via[curX][curY + 1] == 0
					&& (blocks[curX][curY + 1] & 0x1280120) == 0) {
				tileQueueX[head] = curX;
				tileQueueY[head] = curY + 1;
				head = (head + 1) % pathLength;
				via[curX][curY + 1] = 4;
				cost[curX][curY + 1] = thisCost;
			}
			// Can go east
			if (curX < 104 - 1 && via[curX + 1][curY] == 0
					&& (blocks[curX + 1][curY] & 0x1280180) == 0) {
				tileQueueX[head] = curX + 1;
				tileQueueY[head] = curY;
				head = (head + 1) % pathLength;
				via[curX + 1][curY] = 8;
				cost[curX + 1][curY] = thisCost;
			}
			// Can go southwest
			if (curX > 0 && curY > 0 && via[curX - 1][curY - 1] == 0
					&& (blocks[curX - 1][curY - 1] & 0x128010e) == 0
					&& (blocks[curX - 1][curY] & 0x1280108) == 0
					&& (blocks[curX][curY - 1] & 0x1280102) == 0) {
				tileQueueX[head] = curX - 1;
				tileQueueY[head] = curY - 1;
				head = (head + 1) % pathLength;
				via[curX - 1][curY - 1] = 3;
				cost[curX - 1][curY - 1] = thisCost;
			}
			// Can go northwest
			if (curX > 0 && curY < 104 - 1 && via[curX - 1][curY + 1] == 0
					&& (blocks[curX - 1][curY + 1] & 0x1280138) == 0
					&& (blocks[curX - 1][curY] & 0x1280108) == 0
					&& (blocks[curX][curY + 1] & 0x1280120) == 0) {
				tileQueueX[head] = curX - 1;
				tileQueueY[head] = curY + 1;
				head = (head + 1) % pathLength;
				via[curX - 1][curY + 1] = 6;
				cost[curX - 1][curY + 1] = thisCost;
			}
			// Can go southeast
			if (curX < 104 - 1 && curY > 0 && via[curX + 1][curY - 1] == 0
					&& (blocks[curX + 1][curY - 1] & 0x1280183) == 0
					&& (blocks[curX + 1][curY] & 0x1280180) == 0
					&& (blocks[curX][curY - 1] & 0x1280102) == 0) {
				tileQueueX[head] = curX + 1;
				tileQueueY[head] = curY - 1;
				head = (head + 1) % pathLength;
				via[curX + 1][curY - 1] = 9;
				cost[curX + 1][curY - 1] = thisCost;
			}
			// can go northeast
			if (curX < 104 - 1 && curY < 104 - 1
					&& via[curX + 1][curY + 1] == 0
					&& (blocks[curX + 1][curY + 1] & 0x12801e0) == 0
					&& (blocks[curX + 1][curY] & 0x1280180) == 0
					&& (blocks[curX][curY + 1] & 0x1280120) == 0) {
				tileQueueX[head] = curX + 1;
				tileQueueY[head] = curY + 1;
				head = (head + 1) % pathLength;
				via[curX + 1][curY + 1] = 12;
				cost[curX + 1][curY + 1] = thisCost;
			}
		}
		return false;
	}

	public static boolean canReach(final RSTile destination,
			final boolean isObject) {
		final Methods m = new Methods();
		return Calculations.canReach(m.getMyPlayer().getLocation(),
				destination, isObject);
	}

	public static boolean canReach(final RSTile source,
			final RSTile destination, final boolean isObject) {
		return Calculations.canReach(
				source.getX() - Bot.getClient().getBaseX(), source.getY()
				- Bot.getClient().getBaseY(), destination.getX()
				- Bot.getClient().getBaseX(), destination.getY()
				- Bot.getClient().getBaseY(), isObject);
	}

	public static Node findNodeByID(final long id) {
		final Node[] nodes = Bot.getClient().getItemDefMRUNodes()
		.getNodeCache().getCache();
		final Node n = nodes[(int) (id & nodes.length - 1)];
		for (Node node = n.getPrevious(); node != n; node = node.getPrevious()) {
			if (node.getID() == id) {
				return node;
			}
		}
		return null;
	}

	/**
	 * @param startX
	 *            the startX (0 < startX < 104)
	 * @param startY
	 *            the startY (0 < startY < 104)
	 * @param destX
	 *            the destX (0 < destX < 104)
	 * @param destY
	 *            the destY (0 < destY < 104)
	 * @param isObject
	 *            if it's an object, it will find path which touches it.
	 * @return
	 */
	public static int getRealDistanceTo(final int startX, final int startY,
			final int destX, final int destY, final boolean isObject) {
		final int[][] via = new int[104][104];
		final int[][] cost = new int[104][104];
		final int[] tileQueueX = new int[4000];
		final int[] tileQueueY = new int[4000];

		for (int xx = 0; xx < 104; xx++) {
			for (int yy = 0; yy < 104; yy++) {
				via[xx][yy] = 0;
				cost[xx][yy] = 99999999;
			}
		}

		int curX = startX;
		int curY = startY;
		via[startX][startY] = 99;
		cost[startX][startY] = 0;
		int head = 0;
		int tail = 0;
		tileQueueX[head] = startX;
		tileQueueY[head++] = startY;
		boolean foundPath = false;
		final int pathLength = tileQueueX.length;
		final int blocks[][] = Bot.getClient().getGroundDataArray()[Bot
		                                                            .getClient().getPlane()].getBlocks();
		while (tail != head) {
			curX = tileQueueX[tail];
			curY = tileQueueY[tail];

			if (!isObject && curX == destX && curY == destY) {
				foundPath = true;
				break;
			} else if (isObject) {
				if (curX == destX && curY == destY + 1 || curX == destX
						&& curY == destY - 1 || curX == destX + 1
						&& curY == destY || curX == destX - 1 && curY == destY) {
					foundPath = true;
					break;
				}
			}
			tail = (tail + 1) % pathLength;

			// Big and ugly block of code
			final int thisCost = cost[curX][curY] + 1;
			if (curY > 0 && via[curX][curY - 1] == 0
					&& (blocks[curX][curY - 1] & 0x1280102) == 0) {
				tileQueueX[head] = curX;
				tileQueueY[head] = curY - 1;
				head = (head + 1) % pathLength;
				via[curX][curY - 1] = 1;
				cost[curX][curY - 1] = thisCost;
			}
			if (curX > 0 && via[curX - 1][curY] == 0
					&& (blocks[curX - 1][curY] & 0x1280108) == 0) {
				tileQueueX[head] = curX - 1;
				tileQueueY[head] = curY;
				head = (head + 1) % pathLength;
				via[curX - 1][curY] = 2;
				cost[curX - 1][curY] = thisCost;
			}
			if (curY < 104 - 1 && via[curX][curY + 1] == 0
					&& (blocks[curX][curY + 1] & 0x1280120) == 0) {
				tileQueueX[head] = curX;
				tileQueueY[head] = curY + 1;
				head = (head + 1) % pathLength;
				via[curX][curY + 1] = 4;
				cost[curX][curY + 1] = thisCost;
			}
			if (curX < 104 - 1 && via[curX + 1][curY] == 0
					&& (blocks[curX + 1][curY] & 0x1280180) == 0) {
				tileQueueX[head] = curX + 1;
				tileQueueY[head] = curY;
				head = (head + 1) % pathLength;
				via[curX + 1][curY] = 8;
				cost[curX + 1][curY] = thisCost;
			}
			if (curX > 0 && curY > 0 && via[curX - 1][curY - 1] == 0
					&& (blocks[curX - 1][curY - 1] & 0x128010e) == 0
					&& (blocks[curX - 1][curY] & 0x1280108) == 0
					&& (blocks[curX][curY - 1] & 0x1280102) == 0) {
				tileQueueX[head] = curX - 1;
				tileQueueY[head] = curY - 1;
				head = (head + 1) % pathLength;
				via[curX - 1][curY - 1] = 3;
				cost[curX - 1][curY - 1] = thisCost;
			}
			if (curX > 0 && curY < 104 - 1 && via[curX - 1][curY + 1] == 0
					&& (blocks[curX - 1][curY + 1] & 0x1280138) == 0
					&& (blocks[curX - 1][curY] & 0x1280108) == 0
					&& (blocks[curX][curY + 1] & 0x1280120) == 0) {
				tileQueueX[head] = curX - 1;
				tileQueueY[head] = curY + 1;
				head = (head + 1) % pathLength;
				via[curX - 1][curY + 1] = 6;
				cost[curX - 1][curY + 1] = thisCost;
			}
			if (curX < 104 - 1 && curY > 0 && via[curX + 1][curY - 1] == 0
					&& (blocks[curX + 1][curY - 1] & 0x1280183) == 0
					&& (blocks[curX + 1][curY] & 0x1280180) == 0
					&& (blocks[curX][curY - 1] & 0x1280102) == 0) {
				tileQueueX[head] = curX + 1;
				tileQueueY[head] = curY - 1;
				head = (head + 1) % pathLength;
				via[curX + 1][curY - 1] = 9;
				cost[curX + 1][curY - 1] = thisCost;
			}
			if (curX < 104 - 1 && curY < 104 - 1
					&& via[curX + 1][curY + 1] == 0
					&& (blocks[curX + 1][curY + 1] & 0x12801e0) == 0
					&& (blocks[curX + 1][curY] & 0x1280180) == 0
					&& (blocks[curX][curY + 1] & 0x1280120) == 0) {
				tileQueueX[head] = curX + 1;
				tileQueueY[head] = curY + 1;
				head = (head + 1) % pathLength;
				via[curX + 1][curY + 1] = 12;
				cost[curX + 1][curY + 1] = thisCost;
			}
		}
		if (foundPath) {
			return cost[curX][curY];
		}
		return -1;
	}

	public static boolean onScreen(final Point check) {
		return Calculations.GAMESCREEN.contains(check.x, check.y);
	}

	/**
	 * Returns the tile height relative to the baseline.
	 * */
	public static int tileHeight(final int x, final int y) {
		final int[][][] ground = Bot.getClient().getGroundIntArray();
		int zidx = Bot.getClient().getPlane();
		final int x1 = x >> 7;
			final int y1 = y >> 7;
			final int x2 = x & 127;
			final int y2 = y & 127;
			// System.out.println((0x7f);

			if (x1 < 0 || y1 < 0 || x1 > 103 || y1 > 103) {
				return 0;
			}

			if (zidx < 3
					&& (2 & Bot.getClient().getGroundByteArray()[1][x1][y1]) == 2) {
				zidx++;
			}

			final int i = ground[zidx][x1 + 1][y1] * x2 + ground[zidx][x1][y1]
			                                                               * (128 - x2) >> 7;
			final int j = ground[zidx][x1 + 1][y1 + 1] * x2
			+ ground[zidx][x1][y1 + 1] * (128 - x2) >> 7;

			return j * y2 + (128 - y2) * i >> 7;
	}

	// Calculations
	public static Point tileToScreen(final int x, final int y, final double dX,
			final double dY, final int height) {
		return Calculations.worldToScreen(
				(x - Bot.getClient().getBaseX() + dX) * 128, (y
						- Bot.getClient().getBaseY() + dY) * 128, height);
	}

	public static Point tileToScreen(final int x, final int y, final int height) {
		return Calculations.tileToScreen(x, y, 0.5, 0.5, height);
	}

	public static Point tileToScreen(final RSTile t) {
		return Calculations.tileToScreen(t.getX(), t.getY(), 0);
	}

	public static Point tileToScreen(final RSTile t, final double dX,
			final double dY, final int height) {
		return Calculations.tileToScreen(t.getX(), t.getY(), dX, dY, height);
	}

	public static Point tileToScreen(final RSTile t, final int h) {
		return Calculations.tileToScreen(t.getX(), t.getY(), h);
	}

	public static Point worldToMinimap(double x, double y) {
		x -= Bot.getClient().getBaseX();
		y -= Bot.getClient().getBaseY();
		final int calculatedX = (int) (x * 4 + 2)
		- Bot.getClient().getMyPlayer().getX() / 32;
		final int calculatedY = (int) (y * 4 + 2)
		- Bot.getClient().getMyPlayer().getY() / 32;

		try {
			final Interface mm = Bot.getClient().getInterfaceCache()[548][65];
	
			final int angle = 0x7ff & Bot.getClient().getMinimapInt2()
			+ (int) Bot.getClient().getMinimapInt1();
			final int actDistSq = calculatedX * calculatedX + calculatedY
			* calculatedY;
	
			final int mmDist = Math.max(mm.getHeight() / 2, mm.getWidth() / 2) + 10;
			if (mmDist * mmDist >= actDistSq) {
				int cs = Calculations.CURVESIN[angle];
				final int fact = 256 + Bot.getClient().getMinimapInt3();
				cs = 256 * cs / fact;
				int cc = Calculations.CURVECOS[angle];
				cc = 256 * cc / fact;
				final int i_25_ = -(calculatedX * cs) + calculatedY * cc >> 16;
				final int i_26_ = calculatedX * cc + calculatedY * cs >> 16;
	
				final int screenx = mm.getX() + mm.getMasterX() + mm.getWidth() / 2
				+ i_26_;
				final int screeny = -i_25_ + mm.getHeight() / 2 + mm.getY()
				+ mm.getMasterY();
	
				// Check if point is within the circel of the minimap instead of the
				// rectangle!
				if (Math.max(i_26_, -i_26_) <= mm.getWidth() / 2.0 * .8
						&& Math.max(i_25_, -i_25_) <= mm.getHeight() / 2 * .8) {
					return new Point(screenx, screeny);
				} else {
					return new Point(-1, -1);
				}
			}
		} catch (NullPointerException npe) {
			// return new Point(-1, -1);
		}

		return new Point(-1, -1);// not on minimap
	}

	/**
	 * Parameters are the local view. 128 per tile. 102x102 tiles.
	 * */
	public static Point worldToScreen(double X, double Y, final int height) {
		X += 5;
		Y -= 11;
		if (X < 128 || Y < 128 || X > 13056 || Y > 13056
				|| Bot.getClient().getGroundIntArray() == null) {
			return new Point(-1, -1);
		}
		int tileCalculation = Calculations.tileHeight((int) X, (int) Y)
		- height;
		X -= Bot.getClient().getCamPosX();
		tileCalculation -= Bot.getClient().getCamPosZ();
		final int curvexsin = Calculations.CURVESIN[Bot.getClient()
		                                            .getCameraCurveX()];
		final int curvexcos = Calculations.CURVECOS[Bot.getClient()
		                                            .getCameraCurveX()];
		Y -= Bot.getClient().getCamPosY();
		final int curveysin = Calculations.CURVESIN[Bot.getClient()
		                                            .getCameraCurveY()];
		final int curveycos = Calculations.CURVECOS[Bot.getClient()
		                                            .getCameraCurveY()];
		int calculation = curvexsin * (int) Y + (int) X * curvexcos >> 16;
			Y = -(curvexsin * (int) X) + (int) Y * curvexcos >> 16;
			X = calculation;
			calculation = curveycos * tileCalculation - curveysin * (int) Y >> 16;
			Y = curveysin * tileCalculation + (int) Y * curveycos >> 16;
			tileCalculation = calculation;
			if (Y < 50) {
				return new Point(-1, -1);
			}

			final int ScreenX = ((int) X << 9) / (int) Y + 256;
			final int ScreenY = (tileCalculation << 9) / (int) Y + 167;

			final Point p = new Point(ScreenX, ScreenY);
			if (Calculations.onScreen(p)) {
				return p;
			}
			return new Point(-1, -1);
	}

	private Calculations() {
	}
}
